/*
 * Copyright (c) 2010-2013 Ruben Chakhmakhchyan. All rights reserved.
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Eclipse Public License
 * Version 1.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://opensource.org/licenses/EPL-1.0 and read it before using this file.
 */

#include "strings.h"

#include <windows.h>
#include "list.h"


wchar_t* String::ToWchar( const char* string )
{
    size_t len = GetLength( string );

    int wcharlen = ::MultiByteToWideChar( CP_ACP, MB_ERR_INVALID_CHARS, string, len, NULL, NULL );
    wchar_t* dest = new wchar_t[ wcharlen + 1 ];
    ::memset( dest, 0, ( wcharlen + 1 ) << 1 );

    if ( ::MultiByteToWideChar( CP_ACP, MB_ERR_INVALID_CHARS, string, len, dest, wcharlen ) == wcharlen )
    {
        return dest;
    }
    delete dest;
    return NULL;
}

size_t String::ToWchar( wchar_t* dest, const char* string )
{
    size_t len = GetLength( string );

    int wcharlen = ::MultiByteToWideChar( CP_ACP, MB_ERR_INVALID_CHARS, string, len, NULL, NULL );
    ::memset( dest, 0, ( wcharlen + 1 ) << 1 );

    if ( ::MultiByteToWideChar( CP_ACP, MB_ERR_INVALID_CHARS, string, len, dest, wcharlen ) == wcharlen )
    {
        return wcharlen;
    }
    return 0;
}


bool String::IsUtf8Ascii( const char* string )
{
    bool result = false;

    if ( string != NULL )
    {
        char* i = const_cast<char*>( string );

        while ( i )
        {
            if ( *( i++ ) > 127 )
            {
                result = false;
                break;
            }
        }
        result = true;
    }
    return result;
}

bool String::IsUtf16Ascii( const wchar_t* string )
{
    size_t i = 1;
    size_t len = GetLength( string ) << 1;

    while ( i < len )
    {
        if ( ( ( char* ) string )[ i ] != 0 ) return false;
        i += 2;
    }

    return true;
}

void String::Copy( char** dest, const char* source )
{
    size_t size = GetLength( source ) + 1;
    char* destString = ( char* ) ::calloc( 1, size );
    ::strcpy( destString, source );

    *dest = destString;
}

size_t String::GetLength( const char* value )
{
    return ::strlen( value );
}

size_t String::GetLength( const wchar_t* value )
{
    return ::wcslen( value );
}

size_t String::ToUtf8( char** result, const wchar_t* value )
{
    return ToUtf8( result, value, ::malloc, ::free );
}

size_t String::ToUtf8( char** result, const wchar_t* value, Allocator allocate, Eraser erase )
{
    size_t length = GetLength( value );

    return ToUtf8( result, value, length, allocate, erase );
}

size_t String::ToUtf8( char** result, const wchar_t* value, size_t length )
{
    return ToUtf8( result, value, length, ::malloc, ::free );
}

size_t String::ToUtf8( char** result, const wchar_t* value, size_t length, Allocator allocate, Eraser erase )
{
    *result = NULL;

    if (value == NULL )
        return 0;

    int bufferSize = ::WideCharToMultiByte( CP_UTF8, 0, value, length, NULL, 0, NULL, NULL );

    if ( bufferSize )
    {
        bufferSize++;
        char* utf8 = ( char* ) allocate( bufferSize );
        ::memset( utf8, 0, bufferSize );

        bufferSize = ::WideCharToMultiByte( CP_UTF8, 0, value, length, utf8, bufferSize, NULL, NULL );

        if ( !bufferSize )
        {
            erase( utf8 );
            utf8 = NULL;
            bufferSize = 0;
        }
        *result = utf8;
    }
    return bufferSize;
}

char* String::Clone( const char* value, Allocator allocate )
{
    size_t len = GetLength( value ) + 1;

    char* c = ( char* ) allocate( len );
    ::memcpy( c, value, len );

    return c;
}

char* String::Clone( const char* value )
{
    return Clone( value, ::malloc );
}

wchar_t* String::Utf8ToWideChar( const char* chars, size_t length )
{
    wchar_t* dest = NULL;

    int wcharlen = ::MultiByteToWideChar( CP_UTF8, MB_ERR_INVALID_CHARS, chars, length, NULL, NULL );

    dest = new wchar_t[ wcharlen + 1 ];
    ::memset( dest, 0, ( wcharlen + 1 ) << 1 );

    if ( ::MultiByteToWideChar( CP_UTF8, MB_ERR_INVALID_CHARS, chars, length, dest, wcharlen ) != wcharlen )
    {
        delete dest;
        dest = NULL;
    }

    return dest;
}

bool String::Equal( const char* string1, const char* string2 )
{
    return !::strcmp( string1, string2 );
}

bool String::Equal( const wchar_t* string1, const wchar_t* string2 )
{
    return !::wcscmp( string1, string2 );
}

struct FormatArg
{
    enum class Type : int
    {
        Bool, Byte, Char, Int, Long, Uint, Ulong, String, WideString,
    } type;

    void* value;
};

void add( List<FormatArg>& argList, const char* value )
{
    FormatArg fa = { FormatArg::Type::String, ( void* ) const_cast<char*>( value ) };
    argList.add( fa );
}

void add( List<FormatArg>& argList, const int value )
{
    FormatArg fa = { FormatArg::Type::String, ( void* ) value };
    argList.add( fa );
}

template<typename T, typename... A>
void Write( List<FormatArg>& argList, const T& value, A... args )
{
    if ( sizeof...( args ) )
    {
        ::add( argList, value );
        ::Write( argList, args );
    }
}

template<typename T, typename... Args>
char* Write( const char* format, Args... args )
{
    List<FormatArg> list( sizeof...( args ) );
    ::Write( list, args );

    auto formatStringLength = String::GetLength( format );
    char* cp = format;

    List<char> result( formatStringLength << 1 );

    bool paramStarted = false;
    char paramIndexString[ 16 ] = { 0 };
    char* ip = paramIndexString;

    for ( int i = 0; i < formatStringLength; i++ )
    {
        switch ( c )
        {
        case '{':
            paramStarted = true;
            ip = paramIndexString;
            break;

        case '}':
            paramStarted = false;

            //Convert

            break;

        default:
            if ( !paramStarted )
                result.add( c );
            break;
        }

        if ( paramStarted )
        {
            *ip++ = c;
        }
    }

    return result.detach();
}
